package com.github.gaojh.ioc.bean;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.github.gaojh.aop.annotation.Aop;
import com.github.gaojh.ioc.annotation.*;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author 高建华
 * @date 2019-04-28 21:25
 */
public class BeanClassScanner implements ClassScanner {

    private static final ConcurrentHashMap<String, ClassInfo> CLASS_INFO_MAP = new ConcurrentHashMap<>();

    public BeanClassScanner(String... basePackages) {
        for (String basePackage : basePackages) {
            List<Class<? extends Annotation>> annotationList = getAnnotationList();
            for (Class<? extends Annotation> aClass : annotationList) {
                Set<Class<?>> classSet = ClassUtil.scanPackageByAnnotation(basePackage, aClass);
                classSet.forEach(clazz -> {
                    String beanName = getBeanName(clazz);
                    CLASS_INFO_MAP.put(beanName, ClassInfo.builder().beanClass(clazz).beanName(beanName).isConfigurationBean(false).build());
                    if (clazz.isAnnotationPresent(Configuration.class)) {
                        Arrays.stream(clazz.getMethods()).filter(method -> method.isAnnotationPresent(Bean.class)).forEach(method -> {
                            String methodBeanName = getBeanName(method.getReturnType());
                            ClassInfo classDefine = ClassInfo.builder().beanName(methodBeanName).beanClass(method.getReturnType()).isConfigurationBean(true).configurationClass(clazz).method(method).build();
                            CLASS_INFO_MAP.put(methodBeanName, classDefine);
                        });
                    }
                });
            }
        }
    }

    private List<Class<? extends Annotation>> getAnnotationList() {
        return CollectionUtil.toList(Configuration.class, Component.class, Controller.class, Setup.class, WebFilter.class, WebSocket.class, Aop.class);
    }

    @Override
    public ClassInfo getClassInfo(String name) {
        return CLASS_INFO_MAP.get(name);
    }

    @Override
    public List<ClassInfo> getClassByAnnotation(Class<? extends Annotation> annotation) {
        return CLASS_INFO_MAP.values().stream().filter(classInfo -> classInfo.getBeanClass().isAnnotationPresent(annotation)).collect(Collectors.toList());
    }

    @Override
    public List<ClassInfo> getClassByInterface(Class<?> interfaceClass) {
        return CLASS_INFO_MAP.values().stream().filter(classInfo -> classInfo.getBeanClass().isAssignableFrom(interfaceClass)).collect(Collectors.toList());
    }

    @Override
    public List<ClassInfo> getAllClass() {
        return new ArrayList<>(CLASS_INFO_MAP.values());
    }

    @Override
    public String getBeanName(Class<?> clazz) {
        if (clazz.isInterface()) {
            List<ClassInfo> classes = getClassByInterface(clazz);
            if (classes == null || classes.isEmpty()) {
                return clazz.getName();
            } else if (classes.size() > 1) {
                String msg = String.format("该接口[%s]的实现类有%d个，应该只能是1个", clazz.getName(), classes.size());
                throw new RuntimeException(msg);
            } else {
                clazz = classes.get(0).getBeanClass();
            }
        }
        String name = clazz.getName();

        if (clazz.isAnnotationPresent(Component.class)) {
            Component component = clazz.getAnnotation(Component.class);
            name = StrUtil.isBlank(component.value()) ? name : component.value();
        } else if (clazz.isAnnotationPresent(Controller.class)) {
            Controller controller = clazz.getAnnotation(Controller.class);
            name = StrUtil.isBlank(controller.value()) ? name : controller.value();
        } else if (clazz.isAnnotationPresent(Configuration.class)) {
            Configuration configuration = clazz.getAnnotation(Configuration.class);
            name = StrUtil.isBlank(configuration.value()) ? name : configuration.value();
        }

        return name;
    }
}
